Day 18: 람다와 함수형 인터페이스
람다 표현식은 익명 함수를 간결하게 표현하는 문법입니다. Java 8부터 도입되었으며, 함수형 프로그래밍 스타일을 가능하게 합니다. 함수형 인터페이스(추상 메서드가 하나인 인터페이스)와 함께 사용됩니다.
람다 표현식 기본 문법
익명 클래스를 람다로 변환하는 과정을 살펴봅니다.
import java.util.Arrays;
import java.util.List;
// 함수형 인터페이스: 추상 메서드가 정확히 하나
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
@FunctionalInterface
interface Printer {
void print(String message);
}
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
public class LambdaBasic {
public static void main(String[] args) {
// 1. 매개변수 두 개, 표현식 본문
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
Calculator max = (a, b) -> Math.max(a, b);
System.out.println("더하기: " + add.calculate(10, 20));
System.out.println("곱하기: " + multiply.calculate(5, 6));
System.out.println("최대값: " + max.calculate(15, 8));
// 2. 매개변수 하나 (괄호 생략 가능)
Printer printer = message -> System.out.println("출력: " + message);
printer.print("안녕하세요!");
// 3. 블록 본문 (여러 줄)
Calculator safeDivide = (a, b) -> {
if (b == 0) {
System.out.println("0으로 나눌 수 없습니다.");
return 0;
}
return a / b;
};
System.out.println("나누기: " + safeDivide.calculate(10, 3));
safeDivide.calculate(10, 0);
// 4. 문자열 처리
StringProcessor toUpper = input -> input.toUpperCase();
StringProcessor addPrefix = input -> "[Java] " + input;
System.out.println(toUpper.process("hello"));
System.out.println(addPrefix.process("람다 표현식"));
// 5. 리스트 정렬에 활용
List<String> names = Arrays.asList("홍길동", "김", "이순신", "강감찬");
names.sort((a, b) -> a.length() - b.length());
System.out.println("길이순: " + names);
}
}
주요 함수형 인터페이스
java.util.function 패키지의 핵심 인터페이스들입니다.
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunctionalInterfaces {
public static void main(String[] args) {
// Function<T, R>: 입력 T -> 출력 R
Function<String, Integer> strLength = String::length;
Function<Integer, String> intToStr = num -> "숫자: " + num;
System.out.println("길이: " + strLength.apply("Hello Java")); // 10
System.out.println(intToStr.apply(42));
// Function 합성
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addBrackets = s -> "[" + s + "]";
Function<String, String> combined = toUpper.andThen(addBrackets);
System.out.println(combined.apply("hello")); // [HELLO]
// Predicate<T>: 입력 T -> boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<String> isNotEmpty = s -> s != null && !s.isEmpty();
System.out.println("4는 짝수? " + isEven.test(4)); // true
System.out.println("빈 문자열? " + isNotEmpty.test("")); // false
// Predicate 결합
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println("4: " + isEvenAndPositive.test(4)); // true
System.out.println("-2: " + isEvenAndPositive.test(-2)); // false
// Consumer<T>: 입력 T -> void (반환 없음)
Consumer<String> logger = msg -> System.out.println("[LOG] " + msg);
Consumer<String> alert = msg -> System.out.println("[ALERT] " + msg);
logger.accept("서버 시작됨");
logger.andThen(alert).accept("중요 이벤트");
// Supplier<T>: 입력 없음 -> 출력 T
Supplier<Double> randomValue = Math::random;
Supplier<String> greeting = () -> "안녕하세요! 현재 시간: " + System.currentTimeMillis();
System.out.println("랜덤: " + randomValue.get());
System.out.println(greeting.get());
}
}
메서드 참조
람다 표현식을 더 간결하게 표현하는 방법입니다.
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
public class MethodReference {
static int add(int a, int b) {
return a + b;
}
static boolean isAdult(int age) {
return age >= 18;
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Java", "Python", "Go", "Rust");
// 람다 vs 메서드 참조 비교
// 1. 정적 메서드 참조 (ClassName::methodName)
// 람다: (a, b) -> Math.max(a, b)
BiFunction<Integer, Integer, Integer> maxFunc = Math::max;
System.out.println("최대값: " + maxFunc.apply(10, 20));
BiFunction<Integer, Integer, Integer> addFunc = MethodReference::add;
System.out.println("합: " + addFunc.apply(5, 3));
// 2. 인스턴스 메서드 참조 (instance::methodName)
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
System.out.println(greeter.apply("Java!")); // Hello, Java!
// 3. 임의 객체의 인스턴스 메서드 참조 (ClassName::methodName)
// 람다: (s) -> s.toUpperCase()
names.stream()
.map(String::toUpperCase) // 각 String 인스턴스의 toUpperCase 호출
.forEach(System.out::println);
// 4. 생성자 참조 (ClassName::new)
Function<String, StringBuilder> sbCreator = StringBuilder::new;
StringBuilder sb = sbCreator.apply("생성자 참조!");
System.out.println(sb);
// 정렬에 메서드 참조 활용
List<String> fruits = Arrays.asList("바나나", "사과", "포도", "딸기");
fruits.sort(String::compareTo);
System.out.println("정렬: " + fruits);
}
}
실전 패턴: 전략 패턴과 함수형 프로그래밍
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class FunctionalPatterns {
// 필터링 유틸리티
static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// 변환 유틸리티
static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 전략을 람다로 주입
List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
List<Integer> bigNumbers = filter(numbers, n -> n > 5);
List<String> strings = map(numbers, n -> "숫자: " + n);
List<Integer> doubled = map(numbers, n -> n * 2);
System.out.println("짝수: " + evenNumbers);
System.out.println("5 초과: " + bigNumbers);
System.out.println("문자열: " + strings);
System.out.println("2배: " + doubled);
// 함수 파이프라인
Function<Integer, Integer> pipeline = ((Function<Integer, Integer>) (n -> n * 2))
.andThen(n -> n + 10)
.andThen(n -> n * n);
System.out.println("파이프라인(5): " + pipeline.apply(5));
// 5 -> 10 -> 20 -> 400
}
}
오늘의 연습문제
-
문자열 변환기:
Function<String, String>타입의 여러 변환 함수(트림, 대문자, 소문자, 역순)를 만들고,andThen으로 연결하여 ” Hello World ” 문자열을 다양하게 변환하세요. -
필터 체인: 정수 리스트에서
Predicate<Integer>를 조합(and, or, negate)하여 “양수이면서 3의 배수” 또는 “10보다 큰 짝수”인 숫자를 필터링하세요. -
간이 이벤트 시스템:
Map<String, List<Consumer<String>>>을 사용하여 이벤트 이름으로 핸들러를 등록하고 발행하는EventBus클래스를 만드세요.on("click", handler),emit("click", data)형태로 사용할 수 있어야 합니다.